// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_WASM_VALUE_TYPE_H_
#define V8_WASM_VALUE_TYPE_H_

#include "src/machine-type.h"
#include "src/wasm/wasm-constants.h"

namespace v8 {
namespace internal {

    template <typename T>
    class Signature;

    namespace wasm {

        enum ValueType : uint8_t {
            kWasmStmt,
            kWasmI32,
            kWasmI64,
            kWasmF32,
            kWasmF64,
            kWasmS128,
            kWasmAnyRef,
            kWasmAnyFunc,
            kWasmNullRef,
            kWasmExceptRef,
            kWasmVar,
        };

        using FunctionSig = Signature<ValueType>;

        inline size_t hash_value(ValueType type) { return static_cast<size_t>(type); }

// TODO(clemensh): Compute memtype and size from ValueType once we have c++14
// constexpr support.
#define FOREACH_LOAD_TYPE(V) \
    V(I32, , Int32, 2)       \
    V(I32, 8S, Int8, 0)      \
    V(I32, 8U, Uint8, 0)     \
    V(I32, 16S, Int16, 1)    \
    V(I32, 16U, Uint16, 1)   \
    V(I64, , Int64, 3)       \
    V(I64, 8S, Int8, 0)      \
    V(I64, 8U, Uint8, 0)     \
    V(I64, 16S, Int16, 1)    \
    V(I64, 16U, Uint16, 1)   \
    V(I64, 32S, Int32, 2)    \
    V(I64, 32U, Uint32, 2)   \
    V(F32, , Float32, 2)     \
    V(F64, , Float64, 3)     \
    V(S128, , Simd128, 4)

        class LoadType {
        public:
            enum LoadTypeValue : uint8_t {
#define DEF_ENUM(type, suffix, ...) k##type##Load##suffix,
                FOREACH_LOAD_TYPE(DEF_ENUM)
#undef DEF_ENUM
            };

            // Allow implicit convertion of the enum value to this wrapper.
            constexpr LoadType(LoadTypeValue val) // NOLINT(runtime/explicit)
                : val_(val)
            {
            }

            constexpr LoadTypeValue value() const { return val_; }
            constexpr unsigned size_log_2() const { return kLoadSizeLog2[val_]; }
            constexpr unsigned size() const { return 1 << size_log_2(); }
            constexpr ValueType value_type() const { return kValueType[val_]; }
            constexpr MachineType mem_type() const { return kMemType[val_]; }

            static LoadType ForValueType(ValueType type)
            {
                switch (type) {
                case kWasmI32:
                    return kI32Load;
                case kWasmI64:
                    return kI64Load;
                case kWasmF32:
                    return kF32Load;
                case kWasmF64:
                    return kF64Load;
                default:
                    UNREACHABLE();
                }
            }

        private:
            const LoadTypeValue val_;

            static constexpr uint8_t kLoadSizeLog2[] = {
#define LOAD_SIZE(_, __, ___, size) size,
                FOREACH_LOAD_TYPE(LOAD_SIZE)
#undef LOAD_SIZE
            };

            static constexpr ValueType kValueType[] = {
#define VALUE_TYPE(type, ...) kWasm##type,
                FOREACH_LOAD_TYPE(VALUE_TYPE)
#undef VALUE_TYPE
            };

            static constexpr MachineType kMemType[] = {
#define MEMTYPE(_, __, memtype, ___) MachineType::memtype(),
                FOREACH_LOAD_TYPE(MEMTYPE)
#undef MEMTYPE
            };
        };

#define FOREACH_STORE_TYPE(V) \
    V(I32, , Word32, 2)       \
    V(I32, 8, Word8, 0)       \
    V(I32, 16, Word16, 1)     \
    V(I64, , Word64, 3)       \
    V(I64, 8, Word8, 0)       \
    V(I64, 16, Word16, 1)     \
    V(I64, 32, Word32, 2)     \
    V(F32, , Float32, 2)      \
    V(F64, , Float64, 3)      \
    V(S128, , Simd128, 4)

        class StoreType {
        public:
            enum StoreTypeValue : uint8_t {
#define DEF_ENUM(type, suffix, ...) k##type##Store##suffix,
                FOREACH_STORE_TYPE(DEF_ENUM)
#undef DEF_ENUM
            };

            // Allow implicit convertion of the enum value to this wrapper.
            constexpr StoreType(StoreTypeValue val) // NOLINT(runtime/explicit)
                : val_(val)
            {
            }

            constexpr StoreTypeValue value() const { return val_; }
            constexpr unsigned size_log_2() const { return kStoreSizeLog2[val_]; }
            constexpr unsigned size() const { return 1 << size_log_2(); }
            constexpr ValueType value_type() const { return kValueType[val_]; }
            constexpr MachineRepresentation mem_rep() const { return kMemRep[val_]; }

            static StoreType ForValueType(ValueType type)
            {
                switch (type) {
                case kWasmI32:
                    return kI32Store;
                case kWasmI64:
                    return kI64Store;
                case kWasmF32:
                    return kF32Store;
                case kWasmF64:
                    return kF64Store;
                default:
                    UNREACHABLE();
                }
            }

        private:
            const StoreTypeValue val_;

            static constexpr uint8_t kStoreSizeLog2[] = {
#define STORE_SIZE(_, __, ___, size) size,
                FOREACH_STORE_TYPE(STORE_SIZE)
#undef STORE_SIZE
            };

            static constexpr ValueType kValueType[] = {
#define VALUE_TYPE(type, ...) kWasm##type,
                FOREACH_STORE_TYPE(VALUE_TYPE)
#undef VALUE_TYPE
            };

            static constexpr MachineRepresentation kMemRep[] = {
#define MEMREP(_, __, memrep, ___) MachineRepresentation::k##memrep,
                FOREACH_STORE_TYPE(MEMREP)
#undef MEMREP
            };
        };

        // A collection of ValueType-related static methods.
        class V8_EXPORT_PRIVATE ValueTypes {
        public:
            static inline bool IsSubType(ValueType expected, ValueType actual)
            {
                return (expected == actual) || (expected == kWasmAnyRef && actual == kWasmNullRef) || (expected == kWasmAnyRef && actual == kWasmAnyFunc) || (expected == kWasmAnyRef && actual == kWasmExceptRef) || (expected == kWasmAnyFunc && actual == kWasmNullRef) ||
                    // TODO(mstarzinger): For now we treat "null_ref" as a sub-type of
                    // "except_ref", which is correct but might change. See here:
                    // https://github.com/WebAssembly/exception-handling/issues/55
                    (expected == kWasmExceptRef && actual == kWasmNullRef);
            }

            static inline bool IsReferenceType(ValueType type)
            {
                // This function assumes at the moment that it is never called with
                // {kWasmNullRef}. If this assumption is wrong, it should be added to the
                // result calculation below.
                DCHECK_NE(type, kWasmNullRef);
                return type == kWasmAnyRef || type == kWasmAnyFunc || type == kWasmExceptRef;
            }

            static byte MemSize(MachineType type)
            {
                return 1 << i::ElementSizeLog2Of(type.representation());
            }

            static int ElementSizeInBytes(ValueType type)
            {
                switch (type) {
                case kWasmI32:
                case kWasmF32:
                    return 4;
                case kWasmI64:
                case kWasmF64:
                    return 8;
                case kWasmS128:
                    return 16;
                case kWasmAnyRef:
                case kWasmAnyFunc:
                case kWasmExceptRef:
                    return kSystemPointerSize;
                default:
                    UNREACHABLE();
                }
            }

            static int ElementSizeLog2Of(ValueType type)
            {
                switch (type) {
                case kWasmI32:
                case kWasmF32:
                    return 2;
                case kWasmI64:
                case kWasmF64:
                    return 3;
                case kWasmS128:
                    return 4;
                default:
                    UNREACHABLE();
                }
            }

            static byte MemSize(ValueType type) { return 1 << ElementSizeLog2Of(type); }

            static ValueTypeCode ValueTypeCodeFor(ValueType type)
            {
                switch (type) {
                case kWasmI32:
                    return kLocalI32;
                case kWasmI64:
                    return kLocalI64;
                case kWasmF32:
                    return kLocalF32;
                case kWasmF64:
                    return kLocalF64;
                case kWasmS128:
                    return kLocalS128;
                case kWasmAnyRef:
                    return kLocalAnyRef;
                case kWasmAnyFunc:
                    return kLocalAnyFunc;
                case kWasmExceptRef:
                    return kLocalExceptRef;
                case kWasmStmt:
                    return kLocalVoid;
                default:
                    UNREACHABLE();
                }
            }

            static MachineType MachineTypeFor(ValueType type)
            {
                switch (type) {
                case kWasmI32:
                    return MachineType::Int32();
                case kWasmI64:
                    return MachineType::Int64();
                case kWasmF32:
                    return MachineType::Float32();
                case kWasmF64:
                    return MachineType::Float64();
                case kWasmAnyRef:
                case kWasmAnyFunc:
                case kWasmExceptRef:
                    return MachineType::TaggedPointer();
                case kWasmS128:
                    return MachineType::Simd128();
                case kWasmStmt:
                    return MachineType::None();
                default:
                    UNREACHABLE();
                }
            }

            static MachineRepresentation MachineRepresentationFor(ValueType type)
            {
                switch (type) {
                case kWasmI32:
                    return MachineRepresentation::kWord32;
                case kWasmI64:
                    return MachineRepresentation::kWord64;
                case kWasmF32:
                    return MachineRepresentation::kFloat32;
                case kWasmF64:
                    return MachineRepresentation::kFloat64;
                case kWasmAnyRef:
                case kWasmAnyFunc:
                case kWasmExceptRef:
                    return MachineRepresentation::kTaggedPointer;
                case kWasmS128:
                    return MachineRepresentation::kSimd128;
                case kWasmStmt:
                    return MachineRepresentation::kNone;
                default:
                    UNREACHABLE();
                }
            }

            static ValueType ValueTypeFor(MachineType type)
            {
                switch (type.representation()) {
                case MachineRepresentation::kWord8:
                case MachineRepresentation::kWord16:
                case MachineRepresentation::kWord32:
                    return kWasmI32;
                case MachineRepresentation::kWord64:
                    return kWasmI64;
                case MachineRepresentation::kFloat32:
                    return kWasmF32;
                case MachineRepresentation::kFloat64:
                    return kWasmF64;
                case MachineRepresentation::kTaggedPointer:
                    return kWasmAnyRef;
                case MachineRepresentation::kSimd128:
                    return kWasmS128;
                default:
                    UNREACHABLE();
                }
            }

            static char ShortNameOf(ValueType type)
            {
                switch (type) {
                case kWasmI32:
                    return 'i';
                case kWasmI64:
                    return 'l';
                case kWasmF32:
                    return 'f';
                case kWasmF64:
                    return 'd';
                case kWasmAnyRef:
                    return 'r';
                case kWasmAnyFunc:
                    return 'a';
                case kWasmS128:
                    return 's';
                case kWasmStmt:
                    return 'v';
                case kWasmVar:
                    return '*';
                default:
                    return '?';
                }
            }

            static const char* TypeName(ValueType type)
            {
                switch (type) {
                case kWasmI32:
                    return "i32";
                case kWasmI64:
                    return "i64";
                case kWasmF32:
                    return "f32";
                case kWasmF64:
                    return "f64";
                case kWasmAnyRef:
                    return "anyref";
                case kWasmAnyFunc:
                    return "anyfunc";
                case kWasmNullRef:
                    return "nullref";
                case kWasmExceptRef:
                    return "exn";
                case kWasmS128:
                    return "s128";
                case kWasmStmt:
                    return "<stmt>";
                case kWasmVar:
                    return "<var>";
                default:
                    return "<unknown>";
                }
            }

        private:
            DISALLOW_IMPLICIT_CONSTRUCTORS(ValueTypes);
        };

    } // namespace wasm
} // namespace internal
} // namespace v8

#endif // V8_WASM_VALUE_TYPE_H_
